package com.tipfy.tmx;

import flash.utils.ByteArray;
import haxe.io.BytesData;
import haxe.io.Bytes;
import haxe.xml.Fast;
import flash.utils.Endian;

import de.polygonal.core.io.Base64;


class TiledLayer
{
    /**
     * required, length > 0
     */
    public var name:String;

    /**
     * required, nonNegativeInteger
     */
    public var width:Int;

    /**
     * required, nonNegativeInteger
     */
    public var height:Int;

    /**
     * required, decimal
     */
    public var opacity:Float;

    /**
     * encodingT
     */
    public var encoding:String;

    /**
     * compressionT
     */
    public var compression:String;

    /**
     *
     */
    public var properties:TiledProperties;

    /**
     *
     */
    public var tiles:Array<Array<Int>>;

    /**
     * Constructor.
     */
    public function new(xml:Xml)
    {
        // Attributes.
        this.name = TiledAttributes.getNonEmpty(xml, "name");
        this.width = TiledAttributes.getNonNegativeInt(xml, "width");
        this.height = TiledAttributes.getNonNegativeInt(xml, "height");
        this.opacity = TiledAttributes.getFloat(xml, "opacity", null, false);

        // Child elements.
        for (elem in xml.elements())
        {
            switch (elem.nodeName)
            {
                case "properties":
                    this._loadProperties(elem);
                case "data":
                    this._loadData(elem);
                default:
                    throw "Invalid element <" + elem.nodeName + "> in <tile>";
            }
        }
    }

    /**
     * <properties>. minOccurs="0", maxOccurs="1"
     */
    private function _loadProperties(xml:Xml):Void
    {
        if (this.properties != null)
        {
            throw "Duplicated <properties> tag.";
        }

        this.properties = new TiledProperties(xml);
    }

    /**
     * <data>. minOccurs="1", maxOccurs="1"
     */
    private function _loadData(xml:Xml):Void
    {
        if (this.tiles != null)
        {
            throw "Duplicated <data> tag.";
        }

        // TODO: "base64", "csv" or null.
        this.encoding = TiledAttributes.get(xml, "encoding", null, false);
        // TODO: "gzip", "zlib" or null.
        this.compression = TiledAttributes.get(xml, "compression", null,
            false);

        // TODO: child tile element -- see .xsd (unused?)
        // <!-- tile.data.layer -->
        // <xs:element name="tile" minOccurs="0" maxOccurs="unbounded">
        //   <xs:complexType>
        //     <xs:attributeGroup ref="tile.data.layer"/>
        //   </xs:complexType>
        // </xs:element>

        if (this.encoding == null)
        {
            this.tiles = this._getTilesFromXml(xml);
        }
        else
        {
        	trace(xml);
            var data:String = xml.firstChild().toString();
            // Normalize: remove spaces and line breaks.
            // Maybe map files created on Windows use \r\n
            data = StringTools.replace(data, "\r\n", "");
            data = StringTools.replace(data, "\n", "");
            data = StringTools.replace(data, " ", "");

            if (this.encoding == "csv")
            {
                this.tiles = this._getTilesFromCsv(data);
            }
            else if (this.encoding == "base64")
            {
                this.tiles = this._getTilesFromBase64(data);
            }
            else
            {
                throw "Encoding '" + this.encoding + "' is not supported.";
            }
        }

        // Tiles should be loaded by now.
        if (this.tiles == null)
        {
            throw "Tiles weren't loaded.";
        }
    }

    /**
     *
     */
    private function _getTilesFromXml(xml:Xml):Array<Array<Int>>
    {
        var cols = this.width;
        var tiles = new Array<Array<Int>>();
        var tilesRow:Array<Int> = new Array<Int>();
        var colCount:Int = 0;

        for (elem in xml.elements())
        {
            if (elem.nodeName != "tile")
            {
                continue;
            }

            tilesRow.push(TiledAttributes.getInt(elem, "gid"));

            if (++colCount == cols)
            {
                colCount = 0;
                tiles.push(tilesRow);
                tilesRow = new Array<Int>();
            }
        }

        return tiles;
    }

    /**
     *
     */
    private function _getTilesFromCsv(data:String):Array<Array<Int>>
    {
        var cols = this.width;
        var tiles = new Array<Array<Int>>();
        var entries:Array<String> = data.split(",");
        var tilesRow:Array<Int> = new Array<Int>();
        var colCount:Int = 0;

        for (entry in entries)
        {
            tilesRow.push(Std.parseInt(entry));

            if (++colCount == cols)
            {
                colCount = 0;
                tiles.push(tilesRow);
                tilesRow = new Array<Int>();
            }
        }

        return tiles;
    }

    /**
     * Adapted from AS3 lib by Thomas Jahn.
     * Copyright (c) 2010 by Thomas Jahn <lithander@gmx.de>
     * Released under the MIT License.
     */

    private function _getTilesFromBase64(data:String):Array<Array<Int>>
    {
        var tiles:Array<Array<Int>> = new Array<Array<Int>>();
        trace(data);
        
        var bytearray:ByteArray = null;
        #if flash
        bytearray = Base64.decode(data);
        #else
        //This doesn't seem to work, but it allows it to build
        bytearray = ByteArray.fromBytes(Bytes.ofData( Base64.decode(data)));
        #end
        if(bytearray != null)
        {
        if(this.compression == "zlib")
        {
            bytearray.uncompress();
        }
        else if (this.compression != null)
        {
            throw "Compression method '" + this.compression +
                "' is not supported.";
        }

        bytearray.endian = Endian.LITTLE_ENDIAN;
        var cols = this.width;
        var tilesRow:Array<Int> = new Array<Int>();
        var colCount:Int = 0;

        while (bytearray.position < bytearray.length)
        {
            tilesRow.push(bytearray.readInt());

            if (++colCount == cols)
            {
                colCount = 0;
                tiles.push(tilesRow);
                tilesRow = new Array<Int>();
            }
        }
        }
/*        #else
        var test:ByteArray = ByteArray.fromBytes(Bytes.ofData( Base64.decode(data)));
        var cols = this.width;
        var tilesRow:Array<Int> = new Array<Int>();
        var colCount:Int = 0;
        
        trace(result.length);
        for(pos in 0...test.length)
        {
        	var char = test.get(pos);
        	tilesRow.push(char);
        	if (++colCount == cols)
            {
                colCount = 0;
                tiles.push(tilesRow);
                tilesRow = new Array<Int>();
            }
        }
        #end*/
        return tiles;
    }
}
